package com.thinkingcoder.tcrpc.core.provider;

import java.lang.reflect.Method;
import java.net.InetAddress;
import java.util.Arrays;
import java.util.Map;

import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;

import com.thinkingcoder.tcrpc.core.annotation.TcProvider;
import com.thinkingcoder.tcrpc.core.config.AppProperties;
import com.thinkingcoder.tcrpc.core.config.ProviderProperties;
import com.thinkingcoder.tcrpc.core.meta.InstanceMeta;
import com.thinkingcoder.tcrpc.core.meta.ProviderMeta;
import com.thinkingcoder.tcrpc.core.meta.ServiceMeta;
import com.thinkingcoder.tcrpc.core.registry.RegistryCenter;
import com.thinkingcoder.tcrpc.core.util.MethodUtils;

import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
import lombok.Data;
import lombok.Getter;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;

/**
 * 服务提供者的启动类
 */
@Data
@Slf4j
public class ProviderBootstrap implements ApplicationContextAware {
    // 实现 ApplicationContextAware 接口后，需要实现 setApplicationContext(ApplicationContext applicationContext) 方法，
    // 在用 lombok 的 @Data 注解后，形参命名为 applicationContext 即可
    private ApplicationContext applicationContext;
    private RegistryCenter registryCenter;

    private String port;

    private AppProperties appProperties;

    private ProviderProperties providerProperties;

    // 服务提供者的桩点
    @Getter
    private MultiValueMap<String, ProviderMeta> skeleton = new LinkedMultiValueMap<>();

    private InstanceMeta instance;

    public ProviderBootstrap(String port, AppProperties appProperties, ProviderProperties providerProperties) {
        this.port = port;
        this.appProperties = appProperties;
        this.providerProperties = providerProperties;
    }

    @SneakyThrows
    @PostConstruct // init-method // 这个阶段 Spring上下文还没完全准备好，如果将服务在此阶段注册到zk，其他服务消费者进行调用时，会报错
    public void init() {
        // 获取使用了 @TCProvider 注解的 bean（一般为服务实现类）
        Map<String, Object> providers = applicationContext.getBeansWithAnnotation(TcProvider.class);

        registryCenter = applicationContext.getBean(RegistryCenter.class);

        providers.forEach((k, v) -> genInterface(v));
    }

    @SneakyThrows
    public void start() {
        registryCenter.start();

        String ip = InetAddress.getLocalHost().getHostAddress();
        instance = InstanceMeta.http(ip, Integer.parseInt(port)).addParams(providerProperties.getMetas());
        skeleton.keySet().forEach(this::registerService);
    }

    @PreDestroy
    public void stop() {
        log.info(" ===> unreg all services.");
        skeleton.keySet().forEach(this::unRegisterService);

        registryCenter.stop();
    }

    private void registerService(String service) {
        ServiceMeta serviceMeta = ServiceMeta.builder().app(appProperties.getId())
            .namespace(appProperties.getNamespace()).env(appProperties.getEnv()).name(service).build();
        registryCenter.register(serviceMeta, instance);
    }

    private void unRegisterService(String service) {
        ServiceMeta serviceMeta = ServiceMeta.builder().app(appProperties.getId())
            .namespace(appProperties.getNamespace()).env(appProperties.getEnv()).name(service).build();
        registryCenter.unRegister(serviceMeta, instance);
    }

    private void genInterface(Object serviceImpl) {
        // 获取类的接口
        Arrays.stream(serviceImpl.getClass().getInterfaces()).forEach(interfaceService -> {
            // 获取接口中的方法
            Method[] methods = interfaceService.getMethods();
            for (Method method : methods) {
                // 校验是否是基础方法，基础方法不处理
                if (MethodUtils.checkLocalMethod(method)) {
                    continue;
                }
                createProvider(interfaceService, serviceImpl, method);
            }
        });
    }

    /**
     * 按照接口层级，创建元数据，并放入 skeleton 中
     *
     * @param interfaceService 接口类
     * @param serviceImpl 具体实现类
     * @param method 方法
     */
    private void createProvider(Class<?> interfaceService, Object serviceImpl, Method method) {
        ProviderMeta providerMeta = ProviderMeta.builder().method(method).serviceImpl(serviceImpl)
            .methodSign(MethodUtils.methodSign(method)).build();
        log.info("create a provider: " + providerMeta);
        skeleton.add(interfaceService.getCanonicalName(), providerMeta);
    }

}
